#include "../header/bib_Comunicacion.h"

//Select
extern fd_set todos;
extern int maxSock;

//Prototipos
int handshake(int socketCli, uint8_t *idPlanif, char *fs);
int enviarMensajeRechazo(int sock, char *mensaje);
int enviarMensajeAceptacion(int sock);

int atenderNuevaConexion(int sockEntrada, int *nuevoSock){
	struct sockaddr addrNuevoSocket;
	int nuevoSocket=0;
	uint8_t idPlanif;
	char esFS;

	//Recibir conexión
	if (aceptarConexion(sockEntrada,&nuevoSocket,&addrNuevoSocket)==FIN_ERROR){
		return FIN_ERROR;
	}

	//Realizar Handshake
	if (handshake(nuevoSocket,&idPlanif, &esFS)){
		return FIN_WARNING;
	}

	//Determinar el proceso
	if (!(esFS)){
		//Nuevo planificador

		if (determinarCHS(nuevoSocket)){
			//El planif. se desconecto o rechazo
			return FIN_WARNING;
		}else{
			//Enviar a la lista de planificadores

			//Informar por consola que un planificador intenta conectarse
			imprimirLogNumerico(CONSOLA,"Se conecto un planificador",idPlanif);

			if (agregarPlanificador(nuevoSocket, idPlanif)){
				//El planif. se rechazo por un error
				return FIN_WARNING;
			}
		}
	}else{
		//Nueva conexión del File system, enviar a la lista de FS
		if (agregarFileSystem(nuevoSocket)){
			//El File System se rechazo por evaluarMaxun error
			return FIN_WARNING;
		}
	}

	//Devolver el nuevo socket
	*nuevoSock=nuevoSocket;

	return FIN_OK;
}

//Handshake e identificación de la conexión entrante
int handshake(int socketCli, uint8_t *idPlanif, char *fs){
	struct HEADER_NIPC header;
	char *mensaje;
	char mensajeError[40];
	int cantPlanif;			//Cantidad de planificadores conectados

	//Request del proceso
	if (recibirMensajeNIPCDinamico(socketCli, &header, &mensaje)){
		imprimirLog(WARNING, "Fallo en el Handshake", "Request");
		//Desconectar
		cerrarSocket(socketCli);
		return FIN_WARNING;
	}

	//Evaluar mensaje
	if (header.type==0){
		//Determinar si se trata de un planificador o un FileSystem
		if (header.payloadLength>0){
			//Contiene un identificador, se trata de un planificador

			*fs=0;

			//Recuperar identificador
			memcpy(idPlanif,mensaje,sizeof(uint8_t));

			free(mensaje);

			//Determinar que no este repetido el ID
			if (existeIdPlanif(*idPlanif)){
				//Denegar conexión

				strcpy(mensajeError,"El Id de planificador ya existe");
				imprimirLogNumerico(INFO,"Se ha denegado la conexión a un planificador porque el Id ya esta en uso:",(int) idPlanif);

				enviarMensajeRechazo(socketCli,mensajeError);

				return FIN_WARNING;
			}

			//Reponder al handshake
			if (enviarMensajeAceptacion(socketCli)){
				imprimirLogNumerico(INFO,"Error en el handshake, el planificador se desconecto:",(int) idPlanif);
				return FIN_WARNING;
			}

		}else{
			//Se trata de un FileSystem

			*fs=1;

			//Obtener la cantidad de planificadores
			cantPlanif=obtenerCantidadPlanif();

			//Determinar si se puede aceptar la conexión
			if (cantPlanif>0){

				//Reponder al handshake
				if (enviarMensajeAceptacion(socketCli)){
					imprimirLog(INFO,"Error en el handshake, el File System se desconecto","");
					return FIN_WARNING;
				}

			}else{
				//Denegar conexión

				strcpy(mensajeError,"No hay ningún planificador.");

				imprimirLog(INFO,"Se ha denegado la conexión a un File System por no haber planificador.","");

				enviarMensajeRechazo(socketCli,mensajeError);

				return FIN_WARNING;
			}
		}

	}else{
		//Valor de "Type" inválido, denegar conexión

		//Generar mensaje
		strcpy(mensajeError,"Valor del campo Type inválido.");

		imprimirLogNumerico(INFO,"Se ha denegado la conexión por Type inválido:",(int) header.type);

		enviarMensajeRechazo(socketCli,mensajeError);

		return FIN_WARNING;
	}

	//Conexión aceptada
	return FIN_OK;
}

//Rechazo de handshake
int enviarMensajeRechazo(int sock, char *mensaje){
	struct HEADER_NIPC header;

	header.type=0;
	header.payloadLength=strlen(mensaje);

	//Enviar mensaje
	if (enviarMensajeNIPC(sock,&header,mensaje)){
		return FIN_WARNING;
	}

	//Desconectar
	cerrarSocket(sock);

	return FIN_OK;
}

//Aceptar handshake
int enviarMensajeAceptacion(int sock){
	struct HEADER_NIPC header;

	header.type=0;
	header.payloadLength=0;

	//Enviar mensaje
	if (enviarMensajeNIPC(sock,&header,NULL)){
		//Desconectar
		cerrarSocket(sock);
		return FIN_WARNING;
	}

	//Notificar que se conecto
	return FIN_OK;
}

//Atiende una solicitud entrante de un socket
int atenderNuevoMensaje(int sock){
	struct HEADER_NIPC header;
	char *mensaje;
	int respuesta;

	struct EST_CHS respCHS;
	char mensajeError[25];
	uint32_t idSolic;

	struct PAYLOAD_RESPUESTA_LECTURA respuestaLectura;

	int sockFS=0;
	uint8_t destino;
	uint32_t sectorEspejar;
	char informarEscritura;

	struct stPLANIFICADOR *planif=NULL;

	//Recibir mensaje
	if (recibirMensajeNIPCDinamico(sock, &header, &mensaje)){
		//Se deconecto el socket
		verificarEstadoSistema(sock);
		return FIN_WARNING;
	}

	//Determinar si es un planificador o el File System
	if (esSocketFS(sock)){
		//Solicitud del File System

		//Distribuir a los planificadores
		respuesta=distribuirSolicitud(header, mensaje, DESTINO_FS,sock);
		free(mensaje);

		return respuesta;

	}else{
		//Planificador

		//Determinar si es devuelve el CHS o una respuesta a una solicitud
		if (header.type==TIPO_SECTORES){
			//CHS
			memcpy(&respCHS,mensaje,sizeof(struct EST_CHS));
			free(mensaje);

			//Si el CHS corresponde
			if (compararCHS(sock,respCHS)){
				//El CHS no corresponde, denegar conexion
				strcpy(mensajeError,"El CHS no corresponde.");

				imprimirLog(INFO,"Se ha denegado la conexión de un planificador porque no corresponde el CHS.","");

				enviarMensajeRechazo(sock,mensajeError);

				//Evaluar consecuencias
				verificarEstadoSistema(sock);

				return FIN_WARNING;
			}

			return FIN_OK;
		}

		//Si es una respuesta de lectura
		if (header.type==TIPO_LECTURA){

			//Convertir mensaje
			memcpy(&respuestaLectura,mensaje,sizeof(struct PAYLOAD_RESPUESTA_LECTURA));

			if (quitarSolicitudListaPlanif(&destino, &sockFS, sock, respuestaLectura.id, &sectorEspejar, &informarEscritura)){
				//Error, no se encontro el planificador o la solicitud en la lista de enviados

				free(mensaje);
				return FIN_OK;
			}

			//Copiar en cache
			escribirEnCache(sectorEspejar,respuestaLectura.buffer);

			if (destino==DESTINO_FS){
				//Enviar al FS la lectura, actualizar lista y desconectar
				informarLecturaFS(sockFS, header, mensaje);
			}else{
				//Enviar a escribir al disco correspondiente
				encolarEscrituraEspejo(&respuestaLectura, destino, sectorEspejar);
			}

			free(mensaje);

			return FIN_OK;
		}

		//Si es una respuesta de escritura
		if (header.type==TIPO_ESCRITURA){
			//Convertir
			memcpy(&idSolic,mensaje,sizeof(uint32_t));
			free(mensaje);

			quitarSolicitudListaPlanif(&destino, &sockFS, sock, idSolic, &sectorEspejar,&informarEscritura);

			//Obtener planificador usando el socket
			planif=obtenerPuntPlanif(sock);

			if (planificadorEspejado(planif)){
				//Disco funcional
				if (destino==DESTINO_FS && informarEscritura)
					informarEscrituraFS(sockFS, idSolic);

			}else{
				//Si es un disco espejandose calcular el nuevo valor
				calcularValorEspejadoPunt(planif);
			}

			return FIN_OK;
		}

		//Si es un error
		if (header.type==TIPO_ERROR){
			//Convertir
			memcpy(&idSolic,mensaje,sizeof(uint32_t));
			free(mensaje);

			if (quitarSolicitudListaPlanif(&destino, &sockFS, sock, idSolic, &sectorEspejar, &informarEscritura)){
				//Error, no se encontro el planificador o la solicitud en la lista de enviados

				return FIN_WARNING;
			}

			//Enviar al FS, si corresponde
			if (destino==DESTINO_FS)
				responderErrorFS(sockFS, idSolic);
			else{
				//Error, no se pudo espejar un sector
				imprimirLogNumerico(ERROR, "No se pudo espejar un sector en el planificador",(int)destino);
				//Cerrar la conexion con el planificador y evaluar las consecuencias
				cerrarSocket(sock);
				verificarEstadoSistema(sock);
			}

			return FIN_WARNING;
		}
	}

	return FIN_OK;
}

//Determina la accion que se debe tomar por una inesperada desconexión
void verificarEstadoSistema(int sock){
	//Si es un socket de File System, liberar la estructura de la lista
	if (!(quitarFileSystemLista(sock))){
		//Se trata de un planificador
		liberarYDistribuirCarga(sock);
	}
}

//Responde que ocurrio un error en la operación "idSolic"
void responderErrorFS(int sock, uint32_t idSolic){
	struct HEADER_NIPC header;

	//Generar mensaje
	header.type=TIPO_ERROR;
	header.payloadLength=sizeof(uint32_t);

	enviarMensajeNIPC(sock, &header, (char *) &idSolic);
}

//Responde que la operación de escritura "idSolic" se ejecutó correctamente y se desconecta
void responderEscrituraOkFS(int sock, uint32_t idSolic){
	struct HEADER_NIPC header;

	//Generar mensaje
	header.type=TIPO_ESCRITURA;
	header.payloadLength=sizeof(uint32_t);

	//Enviar al FS que se ejecuto la operación de escritura en todos los discos
	if (enviarMensajeNIPC(sock, &header, (char *) &idSolic)){
		//Error al enviar
		imprimirLogNumerico(ERROR,"No se pudo responder al FS que la solicitud de escritura se realizó",(int)idSolic);
	}
}

//Responde a la operación de lectura al FS y se desconecta
int responderLecturaOkFS(int sock, struct HEADER_NIPC header, char *mensaje){

	//Enviar respuesta original del planificador al File System
	if (enviarMensajeNIPC(sock, &header, mensaje)){
		imprimirLog(ERROR, "Error al enviar el resultado de la lectura al File System","");
		return FIN_WARNING;
	}

	return FIN_OK;
}

//Envia al PPD que se finalizo el proceso de espejado
int informarFinEspejo(int sock){
	struct HEADER_NIPC header;

	header.type=TIPO_FIN_ESPEJO;
	header.payloadLength=0;

	//Enviar respuesta original del planificador al File System
	if (enviarMensajeNIPC(sock, &header, NULL)){
		return FIN_WARNING;
	}

	return FIN_OK;
}

//Realiza una solicitud de lectura para espejar un planificador
int solicitarSector(uint32_t sector,uint8_t idPlanif){
	struct HEADER_NIPC header;
	struct PAYLOAD_LECTURA payload;

	//Generar mensaje
	header.type=TIPO_LECTURA;
	header.payloadLength=sizeof(struct PAYLOAD_LECTURA);

	payload.id=generarIdSolicitud();
	payload.sector=sector;

	return (distribuirSolicitud(header, (char *) &payload, idPlanif, 0));
}

//Envia una solicitud de escritura en el planificador usando la lista seleccionada
int escribirEnPlanif(int idLista, struct stPLANIFICADOR *planif){
	struct stSOLICITUD *puntero;
	struct HEADER_NIPC header;
	char textoAux[100];

	sem_wait(&planif->accesoPlanif);

	//Obtener la solicitud de la lista indicada
	if (idLista==ESCRITURA_ESPEJO){
		puntero=planif->solicitudPendEspejar;
		planif->solicitudPendEspejar=puntero->sgte;
	}else{
		puntero=planif->solicitudPend;
		planif->solicitudPend=puntero->sgte;
	}

	//Determinar si es una escritura o lectura
	if (puntero->escritura != NULL){
		//Escritura
		header.type=TIPO_ESCRITURA;
		header.payloadLength=sizeof(struct PAYLOAD_ESCRITURA);

		//Enviar mensaje
		if (enviarMensajeNIPC(planif->sockPlanif, &header, (char *) puntero->escritura)){
			//Error al enviar

			//Colocar nuevamente la solicitud en la lista correspondiente
			if (idLista==ESCRITURA_ESPEJO){
				puntero->sgte=planif->solicitudPendEspejar;
				planif->solicitudPendEspejar=puntero;
			}else{
				puntero->sgte=planif->solicitudPend;
				planif->solicitudPend=puntero;
			}

			sem_post(&planif->accesoPlanif);

			return FIN_WARNING;
		}

	}else{
		//Lectura
		header.type=TIPO_LECTURA;
		header.payloadLength=sizeof(struct PAYLOAD_LECTURA);

		//Enviar mensaje
		if (enviarMensajeNIPC(planif->sockPlanif, &header, (char *) puntero->lectura)){
			//Error al enviar

			//Colocar nuevamente la solicitud en la lista correspondiente
			//(Solo puede ser en la de pendientes normal)
			puntero->sgte=planif->solicitudPend;
			planif->solicitudPend=puntero;

			sem_post(&planif->accesoPlanif);

			return FIN_WARNING;
		}
	}

	//Si es una escritura para espejar liberar memoria, sino colocar en la cola de enviados
	if (idLista==ESCRITURA_ESPEJO){
		//Informar
		sprintf(textoAux,"Se envio una solicitud de espejar el sector %u al planificador %u",puntero->escritura->sector, planif->idPlanificador);
		imprimirLog(DEBUG, textoAux,"");

		free(puntero->escritura);
		free(puntero);
	}else{
		puntero->sgte=planif->solicitudEnv;
		planif->solicitudEnv=puntero;
	}

	sem_post(&planif->accesoPlanif);

	return FIN_OK;
}

//Verifica el nuevo valor de maximo socket
void evaluarMax(int *max, fd_set *conjunto){
	int i, nuevoMax;

	nuevoMax=1;

	for (i=1; i<*max; i++){
		if (FD_ISSET(i, conjunto)){
			nuevoMax=i+1;
		}
	}

	*max=nuevoMax;
}

void quitarDeSelect(int sock){
	FD_CLR(sock,&todos);
	evaluarMax(&maxSock,&todos);
}
